/*
 * ContextSource.java
 *
 * Created on 17 de noviembre de 2005, 10:02
 *
 */

package ocp.context.source;

import java.net.URL;
import javax.xml.validation.Validator;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Schema;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.Source;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.w3c.dom.Document;
import java.io.*;
import ocp.context.xml.*;
import ocp.context.*;
import java.util.*;


/**
 * A Contextual Factory is in charge of creating the link between the context
 * and the adapters that will retrieve the low level information.
 * A contextual factory takes an xml file and a schema to validate it, the 
 * xml file carries the following information:
 * <ul>
 * <li>the contextual class definition for the information being collected.
 * <li>the adapter definition containing the methods to be called to fill
 * the contextual information.
 * </ul>
 * @author Jesús Damián Jiménez Re e Ignacio Nieto Carvajal.
 */
public class ContextSource implements Serializable {
    
    public static final int ERROR_STATE = 0;
    public static final int OK_STATE = 1;
    
    protected int state = OK_STATE;
    protected ContextAdapterDefinition caDefinition = null;
    protected ContextualInformation ci = null;
    protected Class adapter = null;
    protected SimpleContext sourceContext = null;
    
    public ContextSource() {
        caDefinition = new ContextAdapterDefinition();
        caDefinition.setContextClass(new ContextClass("", null));
    }
    
    /**
     * Creates a new instance of ContextSource 
     */
    public ContextSource(String xmlFile, String xmlSchema) throws XMLValidationException {
        Document document;
        Schema schema;
        
        try
        {
            // Parse an XML document into a DOM tree.
            DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            document = parser.parse(new InputSource(new FileReader(xmlFile)));

            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            schema = factory.newSchema(new File(xmlSchema));
            Validator validator = schema.newValidator();
            validator.validate(new DOMSource(document));
            XMLContextAdapterReader xcar = new XMLContextAdapterReader(xmlFile);
            caDefinition = (ContextAdapterDefinition) xcar.read();
        }
        catch (IOException ioe)
        {
           System.err.println("Error opening the file.");
           throw new XMLValidationException("Error opening the file.");
        }
        catch (ParserConfigurationException pce)
        {
           System.err.println("Parsing error.");
           throw new XMLValidationException("Parsing error of file "+xmlFile+" with schema "+xmlSchema);
        }
        catch (SAXException se)
        {
           se.printStackTrace();
           System.out.println(se);
           System.err.println("Error. Document "+xmlFile+" does not match schema in "+xmlSchema);
           throw new XMLValidationException("Document "+xmlFile+" does not match schema in "+xmlSchema);
        }
        catch (IncompatibleContextualObjectException icoe) {
            System.err.println("Error parsing context adapter contained in "+xmlFile+" with schema "+xmlSchema);
            throw new XMLValidationException("Error parsing context adapter contained in "+xmlFile+" with schema "+xmlSchema);
        }
    }

    /**
     * Creates a new instance of ContextSource 
     */
    public ContextSource(String xmlFile, URL xmlSchema) throws XMLValidationException {
        Document document;
        Schema schema;
        
        try
        {
            // Parse an XML document into a DOM tree.
            DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            document = parser.parse(new InputSource(new FileReader(xmlFile)));

            SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            schema = factory.newSchema(xmlSchema);
            Validator validator = schema.newValidator();
            validator.validate(new DOMSource(document));
            XMLContextAdapterReader xcar = new XMLContextAdapterReader(xmlFile);
            caDefinition = (ContextAdapterDefinition) xcar.read();
        }
        catch (IOException ioe)
        {
           System.err.println("Error opening the file.");
           throw new XMLValidationException("Error opening the file.");
        }
        catch (ParserConfigurationException pce)
        {
           System.err.println("Parsing error.");
           throw new XMLValidationException("Parsing error of file "+xmlFile+" with schema "+xmlSchema);
        }
        catch (SAXException se)
        {
           se.printStackTrace();
           System.out.println("---------------------------------------------");
           System.err.println("Error. Document "+xmlFile+" does not match schema in "+xmlSchema);
           System.err.println(se.getMessage());
           throw new XMLValidationException("Document "+xmlFile+" does not match schema in "+xmlSchema);
        }
        catch (IncompatibleContextualObjectException icoe) {
            System.err.println("Error parsing context adapter contained in "+xmlFile+" with schema "+xmlSchema);
            throw new XMLValidationException("Error parsing context adapter contained in "+xmlFile+" with schema "+xmlSchema);
        }
    }
    
    public ContextSource (String xmlFile, String xmlSchema, SimpleContext sourceContext) throws XMLValidationException  {
        this(xmlFile, xmlSchema);
        this.sourceContext = sourceContext;
    }
    
    public ContextSource (String xmlFile, URL xmlSchema, SimpleContext sourceContext)  throws XMLValidationException {
        this (xmlFile, xmlSchema);
        this.sourceContext = sourceContext;
    }

    public void setSourceContext(SimpleContext sourceContext) { this.sourceContext = sourceContext; }
    
    /**
     * This method returns the context class that describes the contextual
     * information provided by this context factory.
     */
    public ContextClass getContextClass() {
        return caDefinition.getContextClass();
    }

    /** returns the context adapter name this csource takes information from */
    public String getName() { return this.caDefinition.getName(); }
    
    /**
     * This method must be invoked in order to retrieve the contextual 
     * information this context source carries.
     */
    public ContextualInformation getContextualinformation() throws 
            ContextualInformationAbsentException, 
            IncompatibleContextualObjectException {
        
        if (ci == null) {
            ContextClass cc = caDefinition.getContextClass();
            ci = new ContextualInformation(cc, caDefinition.getName(), 
                    caDefinition.getName()+" by "+caDefinition.getOwner()+
                    " (v "+caDefinition.getVersion()+")");
        }
        if (adapter == null) {
            try {
                String adapterClass = caDefinition.getAdapterClass();
                adapter = Class.forName(adapterClass); 
            } catch (ClassNotFoundException cnfe) {
                System.err.println("Adapter "+ caDefinition.getAdapterClass()+" not found. Please check your classpath.");
                throw new ContextualInformationAbsentException("Adapter "+ caDefinition.getAdapterClass() +" not found");
            }
        }
        
        // we invoke the methods in the adapter to fill the contextual information.
        Hashtable methods = caDefinition.getAdapterMethods();
        for (Enumeration e = methods.keys(); e.hasMoreElements();) {
           String methodName = (String) e.nextElement();
            try {
                java.lang.reflect.Method method = adapter.getMethod(methodName);
                Object o = method.invoke(adapter.newInstance());
                if (o.getClass().getName().equals(caDefinition.getAdapterMethodAttribute(methodName).getType().getName()))
                    ci.addAttribute(new Attribute(caDefinition.getAdapterMethodAttribute(methodName), o));
                else System.err.println("Type mismatch in method "+methodName);
            } catch (InstantiationException ie) {
                System.err.println("Unable to instantiate adapter "+caDefinition.getAdapterClass());
            } catch (NoSuchMethodException mnfe) {
                System.err.println("Method "+methodName+" not found in adapter "+caDefinition.getAdapterClass());
            } catch (IllegalAccessException iae) {
                System.err.println("Illegal access happened retrieving information by method "+methodName);
            } catch (java.lang.reflect.InvocationTargetException ite) {
                System.err.println("Exception happened retrieving information by method "+methodName);
            } catch (Exception ie) {
                System.err.println("Contextual error building attribute");
            }
        }
        if (ci.validate()) return ci;
        else throw new IncompatibleContextualObjectException("Contextual Information does not match definition.");
    }
        
}
